﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using System;
using System.Linq;
using System.Text;
using UnityEditor;
using Newtonsoft.Json;

namespace Utility.DataSystem
{
	public static class SettingCenter
	{
		class Settings
		{
			public Type SettingType { get; }
			public Dictionary<string, Setting> dict;
			public string[] Names { get; }

			object _list;

			public Settings (Type settingType, IEnumerable<SettingScriptableObject> settings)
			{
				SettingType = settingType;
				dict = settings.ToDictionary (obj => obj.name, obj => obj.GetSetting ());

				//names
				Names = dict.Keys.ToArray ();

				//create ro list
				var genericType = typeof (ReadOnlyCollection<>).MakeGenericType (SettingType);
				var array = Array.CreateInstance (settingType, dict.Count);
				int i = 0;
				foreach (var s in dict.Values)
				{
					array.SetValue (s, i++);
				}
				_list = Activator.CreateInstance (genericType, array);
			}

			public IReadOnlyList<Setting> GetList ()
			{
				return (IReadOnlyList<Setting>)_list;
			}

			public IReadOnlyList<TSetting> GetList<TSetting> ()
				where TSetting : Setting
			{
				return (IReadOnlyList<TSetting>)_list;
			}
		}

		static ILookup<Type, SettingScriptableObject> settingObjectLookup;
		static Dictionary<Type, Settings> settingDict;
		static StringBuilder sb = new StringBuilder ();

		static SettingCenter ()
		{
			LoadSettings ();
		}

		public static void LoadSettings (bool reload = false)
		{
			if (settingObjectLookup == null || reload)
			{
				settingObjectLookup = (from obj in Resources.LoadAll<SettingScriptableObject> ("")
									   where obj && obj.enabled
									   select obj).ToLookup (obj => obj.SettingType);
				settingDict = new Dictionary<Type, Settings> ();

				//FieldCenter.LoadSettings ();
			}

			foreach (var lookup in settingObjectLookup)
			{
				foreach (var settingScriptableObject in lookup)
				{
					settingScriptableObject.GetSetting (true);
				}
			}
		}

#if UNITY_EDITOR
		[MenuItem ("Settings/重载设定", priority = 100)]
		static void ForceLoadSettings ()
		{
			Debug.Log ("Force reload settings");
			LoadSettings (true);
		}
#endif
		static Settings _GetSettings (Type type)
		{
			if (!settingDict.TryGetValue (type, out var settings))
			{
				if (type.IsSubclassOf (typeof (Setting)))
				{
					settings = new Settings (type, settingObjectLookup[type]);
					settingDict[type] = settings;
				}
			}
			return settings;
		}

		//get setting
		public static Setting GetSetting (Type settingType, string key)
		{
			var settings = _GetSettings (settingType);
			if (settings != null && key != null)
			{
				if (settings.dict.TryGetValue (key, out var setting))
				{
					return setting;
				}
			}
			return null;
		}

		public static TSetting GetSetting<TSetting> (string key)
			where TSetting : Setting
		{
			return (TSetting)GetSetting (typeof (TSetting), key);
		}

		//get list
		public static IReadOnlyList<Setting> GetSettings (Type settingType)
		{
			var settings = _GetSettings (settingType);
			if (settings != null)
			{
				return settings.GetList ();
			}
			return null;
		}

		public static IReadOnlyList<TSetting> GetSettings<TSetting> ()
			where TSetting : Setting
		{
			var settings = _GetSettings (typeof (TSetting));
			if (settings != null)
			{
				return settings.GetList<TSetting> ();
			}
			return null;
		}

		//get names
		public static string[] GetNameArray (Type settingType)
		{
			var settings = _GetSettings (settingType);
			if (settings != null)
			{
				return settings.Names;
			}
			return null;
		}

		public static string[] GetNameArray<TSetting> ()
			where TSetting : Setting
		{
			return GetNameArray (typeof (TSetting));
		}

		#region utility

		public static IEnumerable<TSetting> GetSettings<TSetting> (Func<TSetting, bool> predicate)
			where TSetting : Setting
		{
			return GetSettings<TSetting> ().Where (predicate);
		}



		#endregion

		#region debug
		public static void LogSettings (Type settingType)
		{
			sb.Clear ();
			var list = GetSettings (settingType);
			sb.AppendLine ($"{settingType.Name}[{list?.Count ?? 0}]");
			if (list != null)
			{
				foreach (var setting in list)
				{
					sb.AppendLine (setting.SettingName);
				}
			}
			Debug.Log (sb);
			sb.Clear ();
		}

		public static void LogSettings<TSetting> ()
			where TSetting : Setting
		{
			LogSettings (typeof (TSetting));
		}

		public static void LogAllSettings ()
		{
			foreach (var type in settingDict.Keys)
			{
				LogSettings (type);
			}
		}
		#endregion
	}

#if UNITY_EDITOR
	public class SettingCenterAssetPostprocessor : AssetPostprocessor
	{
		static void OnPostprocessAllAssets (string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
		{
			if (importedAssets.Length > 0 || deletedAssets.Length > 0)
			{
				SettingCenter.LoadSettings (true);
			}
		}
	}
#endif

	public class SettingConverter : JsonConverter<Setting>
	{
		public override Setting ReadJson (JsonReader reader, Type objectType, Setting existingValue, bool hasExistingValue, JsonSerializer serializer)
		{
			var key = (string)reader.Value;
			return SettingCenter.GetSetting (objectType, key);
		}

		public override void WriteJson (JsonWriter writer, Setting value, JsonSerializer serializer)
		{
			writer.WriteValue (value.SettingName);
		}
	}
}